/*
 * Window.cpp
 *
 *  Created on: 2012-8-27
 *      Author: yangentao@gmail.com
 */


#include "Window.h"
#include "SoyWindowClass.h"
#include "Layout.h"
#include "RelativeLayout.h"
#include "LinnerLayout.h"

#define PROP_WINDOW_CLASS  TEXT("__Window")
#define PROP_OLD_WNDPROC  TEXT("__WndProc__")

LRESULT CALLBACK XWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

inline WNDPROC GetWindowProc(HWND hWnd) {
	return (WNDPROC) GetWindowLong(hWnd, GWL_WNDPROC);
}
inline bool IsWindowAtached(HWND w) {
	return &XWndProc == GetWindowProc(w); // (WNDPROC) GetWindowLong(w, GWL_WNDPROC);
}

inline void setSoyWindow(HWND hWnd, Window* w) {
	SetProp(hWnd, PROP_WINDOW_CLASS, w);
}
Window* getSoyWindow(HWND hWnd) {
	return (Window*) GetProp(hWnd, PROP_WINDOW_CLASS);
}
inline void setOldProc(HWND hWnd, WNDPROC oldProc) {
	::SetProp(hWnd, PROP_OLD_WNDPROC, (void*) oldProc);
}
inline WNDPROC getOldProc(HWND hWnd) {
	return (WNDPROC) ::GetProp(hWnd, PROP_OLD_WNDPROC);
}

LRESULT CALLBACK XWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
	Window* w = getSoyWindow(hWnd);
	MsgResult mr = w->fireOnMessage(hWnd, uMsg, wParam, lParam);
	HRESULT r = 0;

	//custom message, return 0
	if (uMsg >= WMX_BEGIN && uMsg <= WMX_END) {
		switch (uMsg) {
		case WMX_CHILD_SETFOCUS: //fallthrough
		case WMX_CHILD_KILLFOCUS:
			if (WS_CHILD & GetWindowStyle(hWnd) ) {
				HWND parent = GetParent(hWnd);
				SendMessage(parent, uMsg, wParam, lParam); //send to parent
			}
			break;
		}
		return 0;
	}

	if (mr.enable) {
		r = mr.hr;
	} else {
		WNDPROC proc = getOldProc(hWnd);
		if (proc) {
			r = CallWindowProc(proc, hWnd, uMsg, wParam, lParam);
		} else {
			r = DefWindowProc(hWnd, uMsg, wParam, lParam);
		}
	}
	if (uMsg == WM_NCDESTROY) {
		w->fireOnMessage(hWnd, WMX_FINISHED, 0, 0);
		w->setWnd(0);
	}
	if (GetWindowStyle(hWnd) & WS_CHILD) {
		HWND parent = GetParent(hWnd);
		switch (uMsg) {
		case WM_CREATE:
			::SendMessage(parent, WMX_CHILD_CREATE, 0, (LPARAM) hWnd);
			break;
		case WM_NCDESTROY:
			::SendMessage(parent, WMX_CHILD_DESTROYED, 0, (LPARAM) hWnd);
			break;
		case WM_SETFOCUS:
			::SendMessage(parent, WMX_CHILD_SETFOCUS, 0, (LPARAM) hWnd);
			break;
		case WM_KILLFOCUS:
			::SendMessage(parent, WMX_CHILD_KILLFOCUS, 0, (LPARAM) hWnd);
			break;
		}
	}

	return r;
}

//hook
HHOOK hook_ = 0;
LRESULT CALLBACK MyCBTProc(int nCode, WPARAM wParam, LPARAM lParam) {
	if (nCode == HCBT_CREATEWND) {
		HWND hWnd = HWND(wParam);
		if (IsWindowAtached(hWnd)) {
			CBT_CREATEWND* p = (CBT_CREATEWND*) (lParam);
			Window* w = (Window*) (p->lpcs->lpCreateParams);
			setSoyWindow(hWnd, w);
			w->setWnd(hWnd);
			w->fireOnMessage(hWnd, WMX_CREATING, 0, (LPARAM) (p->lpcs));
		}
	}
	return CallNextHookEx(hook_, nCode, wParam, lParam);
}

void addWindowCreateHook() {
	hook_ = SetWindowsHookEx(WH_CBT, &MyCBTProc, 0, GetCurrentThreadId());
}
void removeWindowCreateHook() {
	UnhookWindowsHookEx(hook_);
}

WindowParam::WindowParam(bool doubleClick) {
	parent = 0;
	text = L"";
	style = 0;
	exStyle = 0;
	rect.left = 0;
	rect.top = 0;
	rect.right = 400;
	rect.bottom = 300;
	className = getSoyWindowClassName(doubleClick);
}

void WindowParam::FrameParam(LPCTSTR text_, Rect rect_) {
	style =
			WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_OVERLAPPEDWINDOW | WS_VISIBLE;
	exStyle = WS_EX_APPWINDOW | WS_EX_ACCEPTFILES;
	text = text_;
	rect = rect_;
}

void WindowParam::ChildParam(HWND parent_, Rect rect_) {
	style = WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_CHILD | WS_VISIBLE;
	exStyle = 0;
	rect = rect_;
	parent = parent_;
}
void WindowParam::NoClipChildParam(HWND parent_, Rect rect_) {
	style = WS_CHILD | WS_VISIBLE;
	exStyle = 0;
	rect = rect_;
	parent = parent_;
}

void WindowParam::PopupParam(HWND parent_, Rect rect_) {
	style = WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_POPUP | WS_VISIBLE;
	exStyle = 0;
	rect = rect_;
	parent = parent_;
}

Window::Window() :
		autoCapture(false),
		autoFocus(false),
		trackHover(false),
		hoverTime(HOVER_DEFAULT),
		tracked(false),
		fireMouseEnter(false),
		style(0),
		exStyle(0) {
	addMessageListener(this);
	addMessageListener(&Events);
}

Window::~Window() {
	clearWindowListeners();
}
void Window::addStyle(DWORD style_) {
	if (getWnd()) {
		WndUtil::addStyle(style_);
	} else {
		style |= style_;
	}

}
void Window::addExStyle(DWORD exStyle_) {
	if (getWnd()) {
		WndUtil::addExStyle(exStyle_);
	} else {
		exStyle |= exStyle_;
	}
}

void Window::removeStyle(DWORD style_) {
	if (getWnd()) {
		WndUtil::removeStyle(style_);
	} else {
		style &= ~style_;
	}
}
void Window::removeExStyle(DWORD exStyle_) {
	if (getWnd()) {
		WndUtil::removeExStyle(exStyle_);
	} else {
		exStyle &= ~exStyle_;
	}
}
void Window::subclassWindow(HWND hWnd) {
	if (!hWnd) {
		return;
	}
	assert(getSoyWindow(hWnd) ==0);
	setWnd(hWnd);

	WNDPROC preProc = GetWindowProc(hWnd); // ::GetWindowLong(hWnd, GWL_WNDPROC);
	assert(preProc != &XWndProc);
	setOldProc(hWnd, preProc);
	SubclassWindow(hWnd, &XWndProc);
	setSoyWindow(hWnd, this);

	SendMessage(hWnd, WMX_SUBCLASSED, 0, 0);
}

bool Window::create(WindowParam param) {
	HWND w = ::CreateWindowEx(param.exStyle | exStyle, param.className,
			param.text, param.style | style, param.rect.left, param.rect.top,
			param.rect.Width(), param.rect.Height(), param.parent, 0,
			GetModuleHandle(0), (Window*) this);
	setWnd(w);
	return w != 0;
}

bool Window::createChild(WindowParam param) {
	param.style &= ~WS_OVERLAPPED;
	param.style &= ~WS_POPUP;
	param.style |= WS_CHILD;
	return create(param);
}
bool Window::createFrame(WindowParam param) {
	param.style &= ~WS_CHILD;
	param.style &= ~WS_POPUP;
	param.style |= WS_OVERLAPPED;
	return create(param);
}
bool Window::createPopup(WindowParam param) {
	param.style &= ~WS_OVERLAPPED;
	param.style &= ~WS_CHILD;
	param.style |= WS_POPUP;
	return create(param);
}
MsgResult Window::onMessage(HWND hWnd, UINT uMsg, WPARAM wParam,
		LPARAM lParam) {

	switch (uMsg) {
	case WM_SIZE:
		onLayout();
		break;
	case WMX_CHILD_CREATE:
		onLayout();
		break;
	case WMX_CHILD_DESTROYED:
		onLayout();
		break;
	case WM_MOUSEMOVE: {
		if (!fireMouseEnter) {
			fireMouseEnter = true;
			SendMessage(hWnd, WMX_MOUSEENTER, wParam, lParam);
		}
		if (!tracked) {
			TRACKMOUSEEVENT tme = { 0 };
			tme.cbSize = sizeof(tme);
			tme.dwFlags = trackHover ? (TME_HOVER | TME_LEAVE) : TME_LEAVE;
			tme.dwHoverTime = hoverTime;
			tme.hwndTrack = hWnd;
			TrackMouseEvent(&tme);
			tracked = true;
		}
	}
		break;
	case WM_MOUSELEAVE:
		tracked = false;
		fireMouseEnter = false;
		break;
	case WM_MOUSEHOVER: //fallthrough
		tracked = false;
		break;
	case WM_LBUTTONDOWN:
		if (autoCapture) {
			SetCapture(hWnd);
		}
		if (autoFocus) {
			SetFocus(hWnd);
		}
		break;
	case WM_LBUTTONUP:
		if (autoCapture && hWnd == GetCapture()) {
			ReleaseCapture();
		}
		break;
	}
	return NoResult;
}
//all message except onCreating/HCBT_CREATEWND can process by onMessage
MsgResult Window::fireOnMessage(HWND hWnd, UINT uMsg, WPARAM wParam,
		LPARAM lParam) {
	MsgResult hr;
	if (!msgSignal.empty()) {
		hr = msgSignal(hWnd, uMsg, wParam, lParam);
	}
	return hr;
}

boost::signals2::connection Window::addMessageHandler(
		const MessageHandler& msgHandler) {
	return msgSignal.connect(msgHandler);
}
boost::signals2::connection Window::addMessageListener(
		WindowListener* listener) {
	return addMessageHandler(WindowMessageListenerWraper(listener));
}

void Window::clearWindowListeners() {
	msgSignal.disconnect_all_slots();
}

void Window::setAutoCapture(bool autoCapture_) {
	autoCapture = autoCapture_;
}

void Window::setAutoFocus(bool autoFocus_) {
	autoFocus = autoFocus_;
}
void Window::setAutoTrackHover(bool track, DWORD hoverTime_) {
	trackHover = track;
	hoverTime = hoverTime_;
}

void Window::setTabStop(bool tab) {
	tab ? addStyle(WS_TABSTOP) : removeStyle(WS_TABSTOP);
}
void Window::onLayout() {
	if (layout) {
		layout->layout(this);
	}
}

Vector<Window*> Window::getChildren(bool withLayoutParam,
		LayoutParamType typeCheck) {
	Vector<Window*> children(16);
	Window* wChild = 0;
	HWND child = getFirstChild();
	while (child) {
		wChild = getSoyWindow(child);
		if (wChild) {
			if (withLayoutParam) {
				if (wChild->layoutParam) {
					assert(wChild->layoutParam->isType(typeCheck));
					children.append(wChild);
				}
			} else {
				children.append(wChild);
			}
		}
		child = GetNextSibling(child);
	}

	return children;
}

void Window::setLayoutParam(boost::shared_ptr<LayoutParam> param) {
	layoutParam = param;
}
void Window::setLayoutParam(boost::shared_ptr<RelativeParam> param) {
	layoutParam = boost::dynamic_pointer_cast<LayoutParam>(param);
}
void Window::setLayoutParam(boost::shared_ptr<LinnerParam> param) {
	layoutParam = boost::dynamic_pointer_cast<LayoutParam>(param);
}

LayoutParam* Window::getLayoutParam() {
	return layoutParam.get();
}

RelativeParam* Window::getRelativeParam() {
	return (RelativeParam*) getLayoutParam();
}
LinnerParam* Window::getLinnerParam() {
	return (LinnerParam*) getLayoutParam();
}

void Window::setLayout(boost::shared_ptr<Layout> layout_) {
	layout = layout_;
}
Layout* Window::getLayout() {
	return layout.get();
}

void Window::setLayout(boost::shared_ptr<LinnerLayout> linnerLayout) {
	setLayout(boost::dynamic_pointer_cast<Layout>(linnerLayout));
}
void Window::setLayout(boost::shared_ptr<RelativeLayout> relativeLayout) {
	setLayout(boost::dynamic_pointer_cast<Layout>(relativeLayout));
}
LinnerLayout* Window::getLinnerLayout() {
	boost::shared_ptr<LinnerLayout> ly = boost::dynamic_pointer_cast<
			LinnerLayout>(layout);
	return ly.get();
}

RelativeLayout* Window::getRelativeLayout() {
	boost::shared_ptr<RelativeLayout> ly = boost::dynamic_pointer_cast<
			RelativeLayout>(layout);
	return ly.get();
}
